﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ServerListerner.Connections
{
    public class TCPConnection : ITCPConnection
    {
        private Socket _socket;
        private byte[] _buffer;
        private int _bufferLen;
        private int _sendBufferLen;
        private List<byte> _received = new List<byte>();
        private bool IsClosed = false;
        public Action<ITCPConnection> OnConnection { get; set; }
        public Action<ITCPConnection, byte[]> OnReceived { get; set; }
        public Action<ITCPConnection> OnClosed { get; set; }
        public Action<ITCPConnection, string, Exception> OnError { get; set; }
        public Guid? ConnectionId { get; private set; }
        public EndPoint? RemoteEndPoint { get; private set; }

        public TCPConnection(Socket socket, Action<ITCPConnection> onConnection)
        {
            this._socket = socket;
            RemoteEndPoint = this._socket.RemoteEndPoint;
            this.ConnectionId = Guid.NewGuid();
            this._bufferLen = this._socket.ReceiveBufferSize;
            this._sendBufferLen= this._socket.SendBufferSize;
            this._buffer = new byte[this._bufferLen];
            onConnection.Invoke(this);
            OnConnection?.Invoke(this);
            StartReceived();
        }
        private void StartReceived()
        {
            try
            {
                this._buffer = null;
                this._buffer = new byte[this._bufferLen];
                if (!this._socket.Connected) this.Close();
                if (IsClosed) return;
                this._socket.BeginReceive(this._buffer, 0, _bufferLen, SocketFlags.None, Receiveding, null);
            }
            catch (Exception ex)
            {
                OnError?.Invoke(this, "Received Error", ex);
            }
        }
        private void Receiveding(IAsyncResult iar)
        {
            try
            {
                if (!this._socket.Connected) this.Close();
                if (IsClosed) return;
                int _readLen = this._socket.EndReceive(iar);
                if (_readLen == 0) Close();
                if (IsClosed) return;
                if (this._received == null)
                    this._received = new List<byte>();
                this._received.AddRange(this._buffer.Take(_readLen));
                if (this._socket.Available <= 0)
                {
                    OnReceived?.Invoke(this, this._received.ToArray());
                    this._received?.Clear();
                }
            }
            catch (Exception ex)
            {
                this._received?.Clear();
                OnError?.Invoke(this, "Received Error", ex);
            }
            StartReceived();

        }
        public void Send(byte[] bytes)
        {
            try
            {
                if (!_socket.Connected) Close();
                if (IsClosed) return;
                var send = bytes.Take(this._sendBufferLen).ToArray();
                int len = send.Length > this._sendBufferLen ? this._sendBufferLen : send.Length;
                var data = bytes.Skip(len).ToArray();
                _socket.BeginSend(send, 0, len, SocketFlags.None, iar =>
                {
                    if (this._socket.Connected)
                    {
                        byte[] sendByte = (byte[])iar.AsyncState;
                        int size = this._socket.EndSend(iar);
                        if (bytes.Length > 0)
                            this.Send(sendByte);
                    }
                     //_socket.EndSend(iar);
                }, data);
            }
            catch (Exception ex)
            {
                OnError?.Invoke(this, "Send Error", ex);
            }
        }
        public void Send(string _str, Encoding encoding = null)
        {
            encoding = encoding ?? Encoding.UTF8;
            this.Send(encoding.GetBytes(_str));
        }
        protected void CloseReceived() => this.Close();

        private void Close()
        {
            this.IsClosed = true;
            this.OnClosed?.Invoke(this);
            this.CloseSocket();
            this.SetNull();
        }
        private void CloseSocket()
        {
            this._socket.Shutdown(SocketShutdown.Both);
            this._socket.Close();
            this._socket.Dispose();
            GC.Collect();
        }
        private void SetNull()
        {
            //this._buffer = null;
            //this._socket = null;
            OnConnection = null;
            OnReceived = null;
            OnClosed = null;
            OnError = null;
        }

        public void Dispose()
        {
            this.Close();
        }
    }
}
